﻿/* Copyright 2010 Igor Francé
 * 
 * This file is part of ECMADoc (http://ecmadoc.codeplex.com).
 * 
 * ECMADoc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * ECMADoc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with ECMADoc. 
 * If not, see http://www.gnu.org/licenses/.
 */
using System.IO;

namespace EcmaDoc.Core.Items
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics.Contracts;
	using System.Linq;
	using System.Reflection;
	using System.Text.RegularExpressions;
	using System.Xml;

	using EcmaDoc.Core.Configuration;

	/// <summary>
	/// Represents a parsed JavaScript code item.
	/// </summary>
	public abstract class CodeItem : IParserItem
	{
		private static readonly List<Visibility> membersLookupOrder = new List<Visibility> 
		{ 
			Visibility.Public, 
			Visibility.Instance, 
			Visibility.Prototype, 
			Visibility.Static, 
			Visibility.Private
		};

		private string type;

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeItem"/> class, using the sppecified parent item and name.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this code item.</param>
		protected CodeItem(CodeItem parentItem, string name)
			: this(parentItem, name, (Comment) null)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeItem"/> class, using the sppecified parent item, name and comment text.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this code item.</param>
		/// <param name="commentText">The comment text for this code item.</param>
		protected CodeItem(CodeItem parentItem, string name, string commentText)
			: this(parentItem, name, (Comment) null)
		{
			this.Comment = Comment.Create(commentText, this);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeItem"/> class, using the sppecified parent item, name and comment.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this code item.</param>
		/// <param name="comment">The comment for this code item.</param>
		protected CodeItem(CodeItem parentItem, string name, Comment comment)
		{
			this.Visibility = Visibility.Default;
			this.BaseTypes = new List<string>();
			this.Ancestors = new List<List<object>>();
			
			this.Properties = new PropertyCollection();
			this.ItemType = ItemType.Object;

			this.MemberGroups = new MemberCollection();

			this.InheritedMembers = new MemberDictionary();

			this.Parent = parentItem;
			this.Comment = comment;
			this.ParseName(name);
		}

		/// <summary>
		/// Gets the ancestors of this code item.
		/// </summary>
		/// <remarks>
		/// The ancestors property will be calculated by the <see cref="Project"/> after all parsing has been
		/// completed. Each member in this list represents one level of ancestry. Some projects may implement
		/// multiple inheritance, and hence for such projects each level can contain more than one ancestor. For
		/// projects using single inheritance (which is default), each item in this list will contain only a single
		/// ancestor item.
		/// </remarks>
		public List<List<object>> Ancestors
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the base types of this code item.
		/// </summary>
		/// <remarks>
		/// This list contains the signatures of base type(s) of this code item. For single inheritance projects, this
		/// list will only cotain a single item.
		/// </remarks>
		public List<string> BaseTypes
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets this item's comment.
		/// </summary>
		public Comment Comment
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the parser configuration applicable for this code item.
		/// </summary>
		public ParserConfig Config
		{
			get
			{
				return this.OwnerProject.Info.Config;
			}
		}

		/// <summary>
		/// Gets the dynamic name, comprised of all parent items.
		/// </summary>
		/// <value>
		/// The value returned will be in a format similar to <c>[Document].[Namespace].[Class].[Member]</c>.
		/// </value>
		public virtual string ExpandedName
		{
			get
			{
				List<string> segments = new List<string>();
				CodeItem c = this;
				while (c != null)
				{
					segments.Add(c.Name);
					if (c is NamespaceItem || c is DocumentItem)
						break;

					c = c.Parent;
				}

				segments.Reverse();
				return segments.Join(".");
			}
		}

		/// <summary>
		/// Gets the full name of this type (same as <see cref="RawName"/> but with any 'prototype.' parts removed).
		/// </summary>
		/// <value>
		/// The value returned could be something similar to <code>Object.method</code>. For a document, this would be
		/// something like <code>document-name.js</code>.
		/// </value>
		public string FullName
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the list of members inherited from this item's ancestors.
		/// </summary>
		public MemberDictionary InheritedMembers
		{
			get;
			protected set;
		}

		/// <summary>
		/// Gets a value indicating whether this instance is constructor.
		/// </summary>
		public bool IsConstructor
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets or sets a value indicating whether this code item is virtual.
		/// </summary>
		/// <remarks>A 'virtual' item is a item that was discovered in code but whose definition has not yet been 
		/// parsed.</remarks>
		public bool IsVirtual
		{
			get;
			set;
		}

		/// <summary>
		/// Gets a value indicating whether this code item is global.
		/// </summary>
		public bool IsGlobal
		{
			get
			{
				return 
					this.OwnerNamespace == null || 
					this.OwnerNamespace.Name == ParserConfig.GlobalNamespace;
			}
		}

		/// <summary>
		/// Gets the type of this item
		/// </summary>
		public virtual ItemType ItemType
		{ 
			get; 
			internal set;
		}

		/// <summary>
		/// Gets or sets this item's members.
		/// </summary>
		public MemberCollection MemberGroups
		{
			get;
			protected set;
		}

		/// <summary>
		/// Gets the count of members of this item.
		/// </summary>
		public int MemberCount
		{
			get
			{
				return MemberGroups.MemberCount;
			}
		}

		/// <summary>
		/// Gets the count of all members at all levels within this item.
		/// </summary>
		public int MemberCountRecursive
		{
			get
			{
				return MemberGroups.MemberCountRecursive;
			}
		}

		/// <summary>
		/// Gets the members of this item.
		/// </summary>
		public List<CodeItem> Members
		{
			get
			{
				return MemberGroups.Members;
			}
		}

		/// <summary>
		/// Gets all members at all levels within this item.
		/// </summary>
		/// <value>The members recursive.</value>
		public List<CodeItem> MembersRecursive
		{
			get
			{
				return MemberGroups.MembersRecursive;
			}
		}

		/// <summary>
		/// Gets the name of this item.
		/// </summary>
		public virtual string Name
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the prefix of this code item's name (for instance <c>wl.modules</c> for full name <c>wl.modules.TwitterModule</c>).
		/// </summary>
		public virtual string NamePrefix
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the document in which this member has been defined.
		/// </summary>
		public DocumentItem OwnerDocument
		{
			get
			{
				if (this is DocumentItem)
					return (DocumentItem) this;

				CodeItem result = this;
				while (result != null && !(result is DocumentItem))
					result = result.Parent;

				return result as DocumentItem;
			}
		}

		/// <summary>
		/// Gets the project in which this member has been parsed.
		/// </summary>
		public ProjectItem OwnerProject
		{
			get
			{
				if (this is ProjectItem)
					return (ProjectItem) this;

				CodeItem result = this;
				while (result != null && !(result is ProjectItem))
					result = result.Parent;

				return result as ProjectItem;
			}
		}

		/// <summary>
		/// Gets the namespace to which this member belongs.
		/// </summary>
		public NamespaceItem OwnerNamespace
		{
			get
			{
				if (this is NamespaceItem)
					return (NamespaceItem) this;

				CodeItem result = this;
				while (result != null && !(result is NamespaceItem))
					result = result.Parent;

				return result as NamespaceItem;
			}
		}

		/// <summary>
		/// Gets the member that this member overrides.
		/// </summary>
		public CodeItem Overrides
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the code item that is the parent of this code item.
		/// </summary>
		public CodeItem Parent
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the properties of this item.
		/// </summary>
		public PropertyCollection Properties
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the public members of this item.
		/// </summary>
		public virtual Dictionary<string, CodeItem> PublicMembers
		{
			get
			{
				if (MemberGroups.ContainsKey(Visibility.Public))
					return MemberGroups[Visibility.Public];

				return MemberGroups[Visibility.Static];
			}
		}

		/// <summary>
		/// Gets the raw string that defined this type in code.
		/// </summary>
		/// <value>
		/// The value returned could be something similar to <code>Object.prototype.method</code>, or 
		/// <code>this.method1</code>.
		/// </value>
		public string RawName
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets static version of this item's name ([Parent.Name].[this.XmlItemName]).
		/// </summary>
		public virtual string StaticName
		{
			get
			{
				var p = this.Parent;
				if (p != null && !(this is NamespaceItem || this is ProjectItem))
				{
					if (!(p is NamespaceItem && p.Name == ParserConfig.GlobalNamespace))
					{
						return string.Concat(this.Parent.Name, ".", this.XmlItemName);
					}
				}

				return this.XmlItemName;
			}
		}

		/// <summary>
		/// Gets the type of this item.
		/// </summary>
		public string Type
		{
			get
			{
				return this.type;
			}

			internal set
			{
				if (this.Name != value)
					this.AddBaseType(value);

				this.type = value;
			}
		}

		/// <summary>
		/// Gets the url of this item's type.
		/// </summary>
		public string TypeUrl
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the documentation URL of this code item.
		/// </summary>
		public abstract string Url
		{
			get;
		}

		/// <summary>
		/// Gets the visibility of this item.
		/// </summary>
		public Visibility Visibility
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets the name of this item as it will be saved when creating the XML element that represents this item.
		/// </summary>
		public virtual string XmlItemName
		{
			get
			{
				return Name;
			}
		}

		/// <summary>
		/// Adds the specified <paramref name="property"/> to this item's property collection.
		/// </summary>
		/// <param name="property">The property to add.</param>
		public virtual void AddProperty(ItemProperty property)
		{
			if (!this.Properties.ContainsKey(property.Name))
				this.Properties.Add(property.Name, new List<ItemProperty>());

			if (!this.Properties.Contains(property.Name, property))
				this.Properties[property.Name].Add(property);
		}

		/// <summary>
		/// Adds a base type to this code item's <see cref="BaseTypes"/> list.
		/// </summary>
		/// <param name="typeName">The signature of the base type.</param>
		public virtual void AddBaseType(string typeName)
		{
			if (OwnerProject.Info.UseMultipleInheritance && !BaseTypes.Contains(typeName))
			{
				if (BaseTypes.Contains(ParserConfig.DefaultBaseType))
					BaseTypes.Remove(ParserConfig.DefaultBaseType);

				BaseTypes.Add(typeName);
			}
			else if (!BaseTypes.Contains(typeName))
			{
				BaseTypes.Clear();
				BaseTypes.Add(typeName);
			}
		}

		/// <summary>
		/// Adds the specified <paramref name="member"/> to this code item.
		/// </summary>
		/// <param name="memberName">The name of the member being added.</param>
		/// <param name="member">The member being added.</param>
		/// <returns>The member that got added.</returns>
		public virtual CodeItem AddMember(string memberName, CodeItem member)
		{
			return this.AddMember(memberName, Visibility.Public, member);
		}

		/// <summary>
		/// Adds the specified <paramref name="member"/> with the specified <paramref name="visibility"/> and <paramref name="memberName"/>.
		/// </summary>
		/// <param name="memberName">The name of the member being added.</param>
		/// <param name="visibility">The visibility of the member being added.</param>
		/// <param name="member">The member being added.</param>
		/// <returns>The member that got added.</returns>
		public virtual CodeItem AddMember(string memberName, Visibility visibility, CodeItem member)
		{
			if (MemberGroups[visibility].ContainsKey(memberName))
			{
				CodeItem currentMember = this.MemberGroups[visibility][memberName];
				this.MergeCodeItems(currentMember, member, visibility);
			}
			else
			{
				this.MemberGroups[visibility].Add(memberName, member);
			}

			CodeItem result = this.MemberGroups[visibility][memberName];
			result.Parent = this;
			return result;
		}

		/// <summary>
		/// Returns a member with the specified name.
		/// </summary>
		/// <param name="memberName">The name of the member to return.</param>
		/// <returns>The member with the specified name.</returns>
		/// <remarks>The order of searching is:
		/// <list type="number">
		/// <item><term>Prototype</term></item>
		/// <item><term>Static</term></item>
		/// <item><term>Private</term></item>
		/// </list>
		/// </remarks>
		public virtual CodeItem GetMember(string memberName)
		{
			return (from vis in membersLookupOrder
			        where this.MemberGroups[vis].ContainsKey(memberName)
			        select this.MemberGroups[vis][memberName]).FirstOrDefault();
		}

		/// <summary>
		/// Gets the member with the specified name and visibility.
		/// </summary>
		/// <param name="memberName">The name of the member to return.</param>
		/// <param name="visibility">The <see cref="Visibility"/> group within <see cref="MemberGroups"/>.</param>
		/// <returns>The member with the specified name and visibility.</returns>
		public virtual CodeItem GetMember(string memberName, Visibility visibility)
		{
			if (this.HasMember(memberName, visibility))
			{
				return this.MemberGroups[visibility][memberName];
			}

			return null;
		}

		/// <summary>
		/// Determines whether this item has the member with the specified name.
		/// </summary>
		/// <param name="memberName">The name of the member.</param>
		/// <returns>
		/// <c>true</c> if this item has the member; otherwise, <c>false</c>.
		/// </returns>
		public virtual bool HasMember(string memberName)
		{
			return this.GetMember(memberName) != null;
		}

		/// <summary>
		/// Determines whether this item has the member with specified name and visibility.
		/// </summary>
		/// <param name="memberName">Name of the member.</param>
		/// <param name="visibility">The visibility of the member.</param>
		/// <returns>
		/// <c>true</c> if this item has the member; otherwise, <c>false</c>.
		/// </returns>
		public virtual bool HasMember(string memberName, Visibility visibility)
		{
			return this.MemberGroups.ContainsKey(visibility) && this.MemberGroups[visibility].ContainsKey(memberName);
		}

		/// <summary>
		/// Removes the member with the specified <paramref name="memberName"/>.
		/// </summary>
		/// <param name="memberName">The name of the member to remove.</param>
		/// <returns>The member that got removed.</returns>
		public virtual CodeItem RemoveMember(string memberName)
		{
			CodeItem item = null;
			foreach (Visibility visibility in this.MemberGroups.Keys)
				if (this.MemberGroups[visibility].ContainsKey(memberName))
					item = RemoveMember(memberName, visibility);

			return item;
		}

		/// <summary>
		/// Removes the member with the specified <paramref name="memberName"/> from the specified <paramref name="visibility"/> group.
		/// </summary>
		/// <param name="memberName">The name of the member to remove.</param>
		/// <param name="visibility">The visibility group from which to remove the member.</param>
		/// <returns>The member that got removed.</returns>
		public virtual CodeItem RemoveMember(string memberName, Visibility visibility)
		{
			CodeItem item = null;
			if (this.MemberGroups[visibility].ContainsKey(memberName))
			{
				item = this.MemberGroups[visibility][memberName];
				this.MemberGroups[visibility].Remove(memberName);
			}

			return item;
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="String"/> that represents this instance.
		/// </returns>
		public override string ToString()
		{
			return String.Format("{0}.{1}", this.ItemType, this.FullName);
		}

		/// <summary>
		/// Creates an XML node that represents this item.
		/// </summary>
		/// <param name="document">The owner document to use for creating the node.</param>
		/// <returns>An XML node that represents this item.</returns>
		public XmlElement ToXml(XmlDocument document)
		{
			return this.ToXml(document, true);
		}

		/// <summary>
		/// Gets a version of the specified <paramref name="name"/> that can be used as a file name.
		/// </summary>
		/// <param name="name">The code item name.</param>
		/// <returns>A version of the specified <paramref name="name"/> that can be used as a file name.</returns>
		internal static string GetValidFileName(string name)
		{
			if (string.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");

			foreach (char c in Path.GetInvalidFileNameChars())
				name = name.Replace(c, '_');

			foreach (char c in Path.GetInvalidPathChars())
				name = name.Replace(c, '_');

			return name;
		}

		/// <summary>
		/// Gets a three-character file name suffix, based on the specified <paramref name="visibility"/>
		/// </summary>
		/// <param name="visibility">The visibility of the member for which to get the suffix.</param>
		/// <returns>A three-character file name suffix, based on the specified <paramref name="visibility"/></returns>
		/// <remarks>
		/// The suffix is needed to ensure unique file names in cases where a member with the same name is defined in different
		/// scopes (for instance, a class could have both a static and a prototype method called 'getName').
		/// </remarks>
		internal static string GetFileNameSuffix(Visibility visibility)
		{
			switch (visibility)
			{
				case Visibility.Instance:
					return "_in";
				case Visibility.Private:
					return "_pr";
				case Visibility.Prototype:
					return "_pt";
				case Visibility.Public:
					return "_pu";
				case Visibility.Static:
					return "_st";
			}

			return string.Empty;
		}

		/// <summary>
		/// Gets the name of the xml node that represents this code item.
		/// </summary>
		/// <returns>The <see cref="ItemType"/> string converted to lower case.</returns>
		internal virtual string GetNodeName()
		{
			return this.ItemType.ToString().ToLower();
		}

		/// <summary>
		/// Creates a new code item that merges the <paramref name="item1"/> and <paramref name="item2"/>.
		/// </summary>
		/// <param name="item1">The first member to merge.</param>
		/// <param name="item2">The second member to merge.</param>
		/// <returns>A new code item that combines <paramref name="item1"/> and <paramref name="item2"/>.</returns>
		internal CodeItem MergeCodeItems(CodeItem item1, CodeItem item2)
		{
			if (item1 == null)
				throw new ArgumentNullException("item1");
			if (item2 == null)
				throw new ArgumentNullException("item2");

			CodeItem target = item1.IsVirtual ? item2 : item1;
			CodeItem source = target == item1 ? item2 : item1;

			ConstructorInfo ctor = target.GetType().GetConstructor(
				new[] { typeof(CodeItem), typeof(string), typeof(Comment) });

			CodeItem resultItem = (CodeItem)
				ctor.Invoke(new object[] { target.Parent, target.RawName, target.Comment });

			CopyMembers(target, resultItem);
			CopyMembers(source, resultItem);

			resultItem.Type = target.Type ?? source.Type;
			resultItem.Comment = target.Comment ?? source.Comment;
			resultItem.ItemType = target.ItemType;
			resultItem.IsConstructor = target.IsConstructor || source.IsConstructor;

			foreach (string name in target.BaseTypes)
				resultItem.AddBaseType(name);

			foreach (string name in target.Properties.Keys)
				foreach (ItemProperty prop in target.Properties[name])
					resultItem.AddProperty(prop);

			foreach (string name in source.Properties.Keys)
				foreach (ItemProperty prop in source.Properties[name])
					resultItem.AddProperty(prop);

			FunctionItem resultFunction = resultItem as FunctionItem;
			if (resultFunction != null)
			{
				FunctionItem sourceFunction = source as FunctionItem;
				FunctionItem targetFunction = target as FunctionItem;

				resultFunction.Returns = targetFunction.Returns;
				if (sourceFunction != null)
					resultFunction.Returns = sourceFunction.Returns;

				if (!string.IsNullOrEmpty(targetFunction.ArgString))
					resultFunction.ArgString = targetFunction.ArgString;

				if (sourceFunction != null && !string.IsNullOrEmpty(sourceFunction.ArgString))
					resultFunction.ArgString = sourceFunction.ArgString;

				OrderedDictionary<string, Argument> docArgs = targetFunction.DocumentedArguments;
				if (sourceFunction != null && sourceFunction.DocumentedArguments.Count != 0)
				{
					docArgs = sourceFunction.DocumentedArguments;
				}

				foreach (string name in docArgs.Keys)
				{
					resultFunction.DocumentedArguments.Add(name, docArgs[name]);
				}
			}

			if (resultItem is FunctionItem && target is FunctionItem && !string.IsNullOrEmpty(((FunctionItem) target).ArgString))
			{
				((FunctionItem) resultItem).ArgString = ((FunctionItem) target).ArgString;
			}
			else if (resultItem is FunctionItem && source is FunctionItem && !string.IsNullOrEmpty(((FunctionItem) source).ArgString))
			{
				((FunctionItem) resultItem).ArgString = ((FunctionItem) source).ArgString;
			}

			return resultItem;
		}

		/// <summary>
		/// Creates a new code item that merges the <paramref name="item1"/> and <paramref name="item2"/> 
		/// and adds it to this code item's specified member <paramref name="visibility"/> group.
		/// </summary>
		/// <param name="item1">The first member to merge.</param>
		/// <param name="item2">The second member to merge.</param>
		/// <param name="visibility">The visibility group to which to add the merged item.</param>
		/// <returns>
		/// A new code item that combines <paramref name="item1"/> and <paramref name="item2"/>.
		/// </returns>
		internal CodeItem MergeCodeItems(CodeItem item1, CodeItem item2, Visibility visibility)
		{
			if (item1 == null)
				throw new ArgumentNullException("item1");
			if (item2 == null)
				throw new ArgumentNullException("item2");

			CodeItem merged = this.MergeCodeItems(item1, item2);
			if (this.MemberGroups[visibility].ContainsKey(merged.Name))
				this.MemberGroups[visibility][merged.Name] = merged;
			else
				this.MemberGroups[visibility].Add(merged.Name, merged);

			return merged;
		}

		/// <summary>
		/// Moves members from the <paramref name="fromGroup"/> to <paramref name="toGroup"/>.
		/// </summary>
		/// <param name="fromGroup">The visibility group from which to move the members.</param>
		/// <param name="toGroup">The visibility group to which to move the members.</param>
		internal virtual void MoveMembers(Visibility fromGroup, Visibility toGroup)
		{
			if (fromGroup == toGroup)
				throw new ArgumentException("fromGroup and toGroup arguments cannot be the same");

			foreach (string name in MemberGroups[fromGroup].Keys)
				this.AddMember(name, toGroup, this.MemberGroups[fromGroup][name]);

			this.MemberGroups[fromGroup].Clear();
		}

		/// <summary>
		/// Shallow-copies the current code item.
		/// </summary>
		/// <returns>A shalow copy of the current code item.</returns>
		internal virtual CodeItem ShallowCopy()
		{
			return (CodeItem) this.MemberwiseClone();
		}

		/// <summary>
		/// Creates an XML node that represents this item.
		/// </summary>
		/// <param name="document">The owner document to use for creating the node.</param>
		/// <param name="processChildren">If set to <c>true</c>, the child members will be appended as xml elements too.</param>
		/// <returns>An XML node that represents this item.</returns>
		internal virtual XmlElement ToXml(XmlDocument document, bool processChildren)
		{
			string nodeName = this.GetNodeName();
			XmlElement self = document.CreateElement(nodeName);
			self.SetAttribute("name", this.XmlItemName);
			self.SetAttribute("url", this.Url);
			self.SetAttribute("visibility", this.Visibility.ToString().ToLower());
			self.SetAttribute("rawName", this.RawName);
			self.SetAttribute("fullName", this.FullName);
			self.SetAttribute("staticName", this.StaticName);

			var synopsis = this.GetSynopsis(false);
			if (!string.IsNullOrEmpty(synopsis))
			{
				self.SetAttribute("synopsis", synopsis);
				self.SetAttribute("synopsisExtended", this.GetSynopsis(true));
			}

			if (!string.IsNullOrEmpty(this.Type))
				self.SetAttribute("type", this.Type);

			if (!string.IsNullOrEmpty(this.TypeUrl))
				self.SetAttribute("typeUrl", this.TypeUrl);

			if (this.IsVirtual)
				self.SetAttribute("virtual", "true");

			if (this.Overrides != null)
			{
				self.SetAttribute("overridesType", this.Overrides.Name);
				self.SetAttribute("overridesUrl", this.Overrides.Url);
			}

			this.SaveAncestors(self);

			foreach (string name in this.Properties.Keys)
			{
				foreach (ItemProperty prop in this.Properties[name])
				{
					self.AppendChild(prop.ToXml(document));
				}
			}

			var item = this;
			var comment = item.Comment;
			while (comment == null && item.Overrides != null)
			{
				item = item.Overrides;
				comment = item.Comment;
			}

			if (comment != null)
			{
				XmlElement documentation = comment.ToXml(document);
				if (documentation != null)
					self.AppendChild(documentation);
			}

			if (processChildren && this.MemberCount != 0)
				self.AppendChild(this.SaveMembers(document));

			return self;
		}

		internal void UpdateMemberVisibilities()
		{
			foreach (Visibility vis in MemberGroups.Keys)
			{
				Visibility assign = vis;
				if (this.ItemType == ItemType.Object && vis == Visibility.Static && !IsVirtual)
					assign = Visibility.Instance;

				foreach (CodeItem member in MemberGroups[vis].Values)
					member.Visibility = assign;
			}
		}

		/// <summary>
		/// Gets a string that specified a short example of this code item's usage.
		/// </summary>
		/// <param name="extended">If <c>true</c>, the resulting string will be HTML with links to other types.</param>
		/// <returns>A string that specified a short example of this code item's usage</returns>
		protected internal virtual string GetSynopsis(bool extended)
		{
			return null;
		}

		/// <summary>
		/// Saves this code item's ancestors to the specified xml element.
		/// </summary>
		/// <param name="self">The xml element to save the ancestors to.</param>
		protected internal virtual void SaveAncestors(XmlElement self)
		{
			if (this.ItemType != ItemType.Class)
				return;

			if (this.BaseTypes.Count != 0)
			{
				List<List<object>> ancestors = this.Ancestors.Duplicate<List<object>>();
				ancestors.Reverse();

				XmlNode extends = self.AppendChild(self.OwnerDocument.CreateElement("extends"));
				foreach (List<object> level in ancestors)
				{
					XmlNode current = extends.AppendChild(self.OwnerDocument.CreateElement("level"));
					foreach (object ancestor in level)
					{
						string ancestorName = ancestor.ToString();
						string ancestorFullName = ancestorName;
						string ancestorUrl = null;

						if (ancestor is CodeItem)
						{
							CodeItem t = (CodeItem) ancestor;
							ancestorName = t.Name;
							ancestorFullName = t.FullName;
							ancestorUrl = t.Url;
						}

						XmlElement typeNode = (XmlElement) current.AppendChild(self.OwnerDocument.CreateElement("type"));
						typeNode.SetAttribute("name", ancestorName);
						typeNode.SetAttribute("fullName", ancestorFullName);

						if (this.BaseTypes.Contains(ancestorName))
							typeNode.SetAttribute("baseType", "true");

						if (ancestorUrl != null)
							typeNode.SetAttribute("url", ancestorUrl);
					}
				}
			}
		}

		/// <summary>
		/// Created an xml element that contains this code item's members.
		/// </summary>
		/// <param name="document">The xml document to use to create the necessary xml element.</param>
		/// <returns>
		/// The xml element that contains this code item's members.
		/// </returns>
		protected internal virtual XmlElement SaveMembers(XmlDocument document)
		{
			XmlElement membersNode = document.CreateElement("members");

			List<CodeItem> exportMembers = new List<CodeItem>();
			exportMembers.AddRange(Members);
			exportMembers.AddRange(InheritedMembers.Values);

			Dictionary<string, XmlNode> parentNodes = new Dictionary<string, XmlNode>();
			foreach (ItemType groupType in Constants.ExportOrder)
			{
				IEnumerable<CodeItem> groupMembers = exportMembers.Where(m => m.ItemType == groupType);
				if (groupMembers.Count() == 0)
					continue;

				foreach (CodeItem member in groupMembers)
				{
					string itemName = member.GetNodeName();
					if (!Constants.NamePluralizationDictionary.ContainsKey(itemName))
						throw new Exception(string.Format("Unexpected item name encountered: '{0}'", itemName));

					string groupName = Constants.NamePluralizationDictionary[itemName];
					if (!parentNodes.ContainsKey(groupName))
					{
						XmlNode groupNode = membersNode.AppendChild(document.CreateElement(groupName));
						parentNodes.Add(groupName, groupNode);
					}

					XmlElement memberNode = member.ToXml(document);
					if (InheritedMembers.Values.Contains(member))
					{
						memberNode.SetAttribute("inheritedFrom", member.Parent.Name);
						memberNode.SetAttribute("inheritedTypeUrl", member.Parent.Url);
						memberNode.SetAttribute("inheritedMemberUrl", member.Url);
					}

					parentNodes[groupName].AppendChild(memberNode);
				}
			}

			return membersNode;
		}

		/// <summary>
		/// Unserializes the current code item from the specified item node.
		/// </summary>
		/// <param name="itemNode">The item node that contains the previously serialized code item to 
		/// restore into the current code item.</param>
		protected internal virtual void Unserialize(XmlElement itemNode)
		{
			Contract.Requires<ArgumentNullException>(itemNode != null);

			this.ParseName(itemNode.GetAttribute("rawName"));

			this.Type = itemNode.GetAttribute("type");
			this.TypeUrl = itemNode.GetAttribute("typeUrl");
			this.IsVirtual = itemNode.GetAttribute("virtual") == "true";

			XmlElement commentElem = itemNode.SelectSingleNode(Comment.NodeName) as XmlElement;
			this.Comment = Comment.Create(commentElem, this);

			this.Properties = new PropertyCollection();
			foreach (XmlElement propNode in itemNode.SelectNodes(ItemProperty.NodeName))
			{
				ItemProperty prop = ItemProperty.Create(propNode);
				if (!this.Properties.ContainsKey(prop.Name))
					this.Properties.Add(prop.Name, new List<ItemProperty>());

				this.Properties[prop.Name].Add(prop);
			}

			foreach (XmlElement memberNode in itemNode.SelectNodes("members/*/*"))
			{
				string visibility = memberNode.GetAttribute("visibility");
				Visibility visGroup;
				if (!Enum.TryParse(visibility, out visGroup))
					throw new Exception("Unsupported visibility type: " + visibility);

				if (!MemberGroups.ContainsKey(visGroup))
					MemberGroups[visGroup] = new MemberDictionary();

				CodeItem member = CodeItem.Create(OwnerProject.Info, this, memberNode);
				this.AddMember(member.Name, visGroup, member);
			}

			this.BaseTypes = new List<string>();
			foreach (XmlElement typeNode in itemNode.SelectNodes("extends/level/type"))
			{
				if (typeNode.GetAttribute("baseType") == "true")
				{
					this.BaseTypes.Add(typeNode.GetAttribute("fullName"));
				}
			}
		}

		/// <summary>
		/// Parses different name properties of the current code item.
		/// </summary>
		/// <param name="rawName">The raw name of this item.</param>
		protected internal void ParseName(string rawName)
		{
			this.RawName = rawName;
			this.Name = Regex.Replace(rawName, @"^.*\.", string.Empty);
			this.FullName = Regex.Replace(rawName, @"(?<!\.prototype)\.prototype", string.Empty);
			this.NamePrefix = Regex.Replace(this.FullName, String.Format(@"\.{0}$", this.Name), string.Empty);
		}

		/// <summary>
		/// Creates a new <see cref="CodeItem"/> using the specified project info, parent item and item node.
		/// </summary>
		/// <param name="projectInfo">The current project configuration.</param>
		/// <param name="parent">The parent item.</param>
		/// <param name="itemNode">The item node.</param>
		/// <returns>The code item unserialized from the specified <paramref name="itemNode"/>.</returns>
		protected static CodeItem Create(ProjectInfo projectInfo, CodeItem parent, XmlElement itemNode)
		{
			if (projectInfo == null)
				throw new ArgumentNullException("projectInfo");
			if (itemNode == null)
				throw new ArgumentNullException("itemNode");

			CodeItem result;

			string nodeName = itemNode.Name;
			string itemName = itemNode.GetAttribute("name");

			switch (nodeName)
			{
				case "project":
					result = new ProjectItem(projectInfo);
					break;

				case "namespace":
					result = new NamespaceItem(parent, itemName);
					break;

				case "class":
				case "function":
				case "method":
					result = new FunctionItem(parent, itemName);
					break;

				default:
					result = new ObjectItem(parent, itemName);
					break;
			}

			result.Unserialize(itemNode);
			return result;
		}

		private static void CopyMembers(CodeItem from, CodeItem to)
		{
			foreach (var vis in from.MemberGroups.Keys)
			{
				CopyMembers(from, to, vis, vis);
			}
		}

		private static void CopyMembers(CodeItem from, CodeItem to, Visibility visFrom, Visibility visTo)
		{
			if (!to.MemberGroups.ContainsKey(visTo))
				to.MemberGroups.Add(visTo, new MemberDictionary());

			foreach (string name in from.MemberGroups[visFrom].Keys)
				to.AddMember(name, visTo, from.MemberGroups[visFrom][name]);
		}
	}
}